﻿using System;
using System.IO;
using System.Xml.Serialization;
using ArgusXmlSerializer = ArgusLight.Xml.Serialization.XmlSerializer;

namespace ArgusLight.Fractals2D
{
	public struct FractalSaveStruct
	{
		Type FractalType;
		Type FractalParameterType;
		FractalParameter fractalParameter;
		Type ColoringType;
		Type ColoringParameterType;
		ColoringParameter ColoringParameter;

		public FractalParameter FractalParameter { get { return this.fractalParameter; } }

		public FractalSaveStruct(Fractal fractal)
		{
			this.FractalType = fractal.GetType();
			this.fractalParameter = fractal.Parameter;
			this.FractalParameterType = this.fractalParameter.GetType();
			this.ColoringType = fractal.Coloring.GetType();
			this.ColoringParameter = fractal.Coloring.Parameter;
			this.ColoringParameterType = this.ColoringParameter.GetType();
		}

		public Fractal GetFractal()
		{
			Fractal f = (Fractal)Activator.CreateInstance(this.FractalType);
			Coloring c = (Coloring)Activator.CreateInstance(this.ColoringType);
			c.Parameter = this.ColoringParameter;
			f.SetColoring(c);
			return f;
		}

		public void Save(Stream stream)
		{
#if OldFormat
			XmlSerializer xs = new XmlSerializer(typeof(string));
			WriteObject(stream, xs, this.FractalType.AssemblyQualifiedName);
			WriteObject(stream, xs, this.FractalParameterType.AssemblyQualifiedName);

			using (MemoryStream ms = new MemoryStream())
			{
				this.FractalParameter.XmlSerialize(ms);
				WriteMsToStream(ms, stream);
				ms.Close();
			}

			WriteObject(stream, xs, this.ColoringType.AssemblyQualifiedName);
			WriteObject(stream, xs, this.ColoringParameterType.AssemblyQualifiedName);

			using (MemoryStream ms = new MemoryStream())
			{
				this.ColoringParameter.XmlSerialize(ms);
				WriteMsToStream(ms, stream);
				ms.Close();
			}
#else
			ArgusXmlSerializer.Serialize(stream, this.FractalType.AssemblyQualifiedName);
			ArgusXmlSerializer.Serialize(stream, this.FractalParameterType.AssemblyQualifiedName);
			ArgusXmlSerializer.Serialize(stream, this.fractalParameter);
			ArgusXmlSerializer.Serialize(stream, this.ColoringType.AssemblyQualifiedName);
			ArgusXmlSerializer.Serialize(stream, this.ColoringParameterType.AssemblyQualifiedName);
			ArgusXmlSerializer.Serialize(stream, this.ColoringParameter);
#endif
		}

		public static FractalSaveStruct FromStream(Stream stream)
		{
			FractalSaveStruct str = new FractalSaveStruct();
			if (IsOldFormat(stream) == true)
			{
				XmlSerializer xs = new XmlSerializer(typeof(string));

				str.FractalType = Type.GetType((string)ReadObject(stream, xs));
				str.FractalParameterType = Type.GetType((string)ReadObject(stream, xs));

				using (MemoryStream ms = ReadMsFromStream(stream))
				{
					str.fractalParameter = FractalParameter.XmlDeserialize(str.FractalParameterType, ms);
					ms.Close();
				}

				str.ColoringType = Type.GetType((string)ReadObject(stream, xs));
				str.ColoringParameterType = Type.GetType((string)ReadObject(stream, xs));

				if (str.ColoringType == typeof(ComplexFractalColoring.SinusUsingColoring))
					str.ColoringType = typeof(ComplexFractalColoring.PeriodicColoring);
				if (str.ColoringParameterType == typeof(ComplexFractalColoring.SinusUsingColoringParameter))
					str.ColoringParameterType = typeof(ComplexFractalColoring.PeriodicColoringParameter);

				using (MemoryStream ms = ReadMsFromStream(stream))
				{
					str.ColoringParameter = ColoringParameter.XmlDeserialize(str.ColoringParameterType, ms);
					ms.Close();
				}
			}
			else
			{
				str.FractalType = Type.GetType(ArgusXmlSerializer.Deserialize<string>(stream));
				str.FractalParameterType = Type.GetType(ArgusXmlSerializer.Deserialize<string>(stream));
				str.fractalParameter = (FractalParameter)ArgusXmlSerializer.Deserialize(stream, str.FractalParameterType);
				str.ColoringType = Type.GetType(ArgusXmlSerializer.Deserialize<string>(stream));
				str.ColoringParameterType = Type.GetType(ArgusXmlSerializer.Deserialize<string>(stream));

				if (str.ColoringType == typeof(ComplexFractalColoring.SinusUsingColoring))
					str.ColoringType = typeof(ComplexFractalColoring.PeriodicColoring);
				if (str.ColoringParameterType == typeof(ComplexFractalColoring.SinusUsingColoringParameter))
					str.ColoringParameterType = typeof(ComplexFractalColoring.PeriodicColoringParameter);

				str.ColoringParameter = (ColoringParameter)ArgusXmlSerializer.Deserialize(stream, str.ColoringParameterType);
			}
			
			return str;
		}

		private static void WriteObject(Stream stream, XmlSerializer xs, object obj)
		{
			using (MemoryStream ms = new MemoryStream())
			{
				xs.Serialize(ms, obj);
				WriteMsToStream(ms, stream);
				ms.Close();
			}
		}

		private static object ReadObject(Stream stream, XmlSerializer xs)
		{
			object RetVal;
			using (MemoryStream ms = ReadMsFromStream(stream))
			{
				RetVal = xs.Deserialize(ms);
				ms.Close();
			}
			return RetVal;
		}

		private static void WriteMsToStream(MemoryStream ms, Stream stream)
		{
			byte[] length = BitConverter.GetBytes((int)ms.Length);
			byte[] data = new byte[(int)ms.Length];
			ms.Seek(0, SeekOrigin.Begin);
			ms.Read(data, 0, data.Length);
			stream.Write(length, 0, length.Length);
			stream.Write(data, 0, data.Length);
		}

		private static MemoryStream ReadMsFromStream(Stream stream)
		{
			byte[] length = new byte[4];
			stream.Read(length, 0, length.Length);
			int intLength = BitConverter.ToInt32(length, 0);

			MemoryStream ms = new MemoryStream(intLength);
			byte[] data = new byte[intLength];
			stream.Read(data, 0, data.Length);
			ms.Write(data, 0, data.Length);
			ms.Seek(0, SeekOrigin.Begin);
			return ms;
		}

		public static bool IsOldFormat(Stream stream)
		{
			if (stream.Length < 4)
				return false;

			long pos = stream.Position;
			bool RetVal = true;

			byte[] length = new byte[4];
			stream.Read(length, 0, length.Length);
			int intLength = BitConverter.ToInt32(length, 0);

			if (stream.Length < 4 + intLength)
			{
				RetVal = false;
			}
			else
			{
				byte[] data = new byte[intLength];
				stream.Read(data, 0, data.Length);
				if (data[0] != (byte)'ï')
					RetVal = false;
				else if (data[data.Length-1] != (byte)'>')
					RetVal = false;
			}

			stream.Position = pos;
			return RetVal;
		}
	}
}
